package com.hlab.fabrevealmenu.view;

import com.hlab.fabrevealmenu.ResourceTable;
import com.hlab.fabrevealmenu.helper.AnimationHelper;
import com.hlab.fabrevealmenu.helper.Direction;
import com.hlab.fabrevealmenu.helper.OnFABMenuSelectedListener;
import com.hlab.fabrevealmenu.helper.ViewHelper;
import com.hlab.fabrevealmenu.model.FABMenuItem;
import com.hlab.fabrevealmenu.utils.TypedAttrUtils;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.ListContainer;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.text.Font;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;

import java.io.IOException;
import java.util.ArrayList;

/**
 * FABRevealMenu
 *
 * @since 2021-05-14
 */
public class FABRevealMenu extends StackLayout {
    private static final int INT_DEFAULT = -1;
    private static final int INT_10 = 10;
    private static final int INT_500 = 500;
    private static final int FAB_STATE_COLLAPSED = 0;
    private static final int FAB_STATE_EXPANDED = 1;
    private static final int FAB_MENU_SIZE_NORMAL = 0;
    private static final int FAB_MENU_SIZE_SMALL = 1;

    private OnFABMenuSelectedListener menuSelectedListener = null;
    private Context mContext;
    private ComponentContainer mCustomView;
    private ComponentContainer mFab;

    // attributes
    private int mMenuRes;
    private int mMenuBackground;
    private int mOverlayBackground;
    private boolean isShowOverlay;
    private int mMenuSize;
    private Direction mDirection;
    private boolean isShowTitle;
    private boolean isShowIcon;
    private int mTitleTextColor;
    private int mTitleDisabledTextColor;
    private int fabCurrentState = FAB_STATE_COLLAPSED;
    private int mMenuCornerRadius;
    private int mDuration;
    private Font mMenuTitleTypeface;

    // Views in the menu
    private StackLayout mOverlayLayout = null;
    private DirectionalLayout mRevealView = null;
    private ListContainer mMenuView = null;
    private boolean isEnableNestedScrolling = true;
    private DependentLayout mBaseView = null;
    private FABMenuProvider menuAdapter = null;

    // Menu specific fields
    private ArrayList<FABMenuItem> menuList = null;

    // Helper class
    private ViewHelper viewHelper;
    private AnimationHelper animationHelper;

    /**
     * FABRevealMenu
     *
     * @param context context
     * @param attrSet attrSet
     */
    public FABRevealMenu(Context context, AttrSet attrSet) {
        this(context, attrSet,"");
    }

    /**
     * FABRevealMenu
     *
     * @param context context
     * @param attrSet attrSet
     * @param defStyleAttr defStyleAttr
     */
    public FABRevealMenu(Context context, AttrSet attrSet, String defStyleAttr) {
        super(context, attrSet, defStyleAttr);
        initView(context, attrSet);
    }

    /**
     * OnFABMenuSelectedListener
     *
     * @return OnFABMenuSelectedListener
     */
    public OnFABMenuSelectedListener getMenuSelectedListener() {
        return menuSelectedListener;
    }

    private void initView(Context context, AttrSet attrSet) {
        mContext = context;
        mMenuBackground = TypedAttrUtils.getIntColor(
                attrSet,"menuBackgroundColor", getColor(ResourceTable.Color_colorWhite));
        mOverlayBackground = TypedAttrUtils.getIntColor(
                attrSet,"overlayBackground", getColor(ResourceTable.Color_colorOverlayDark));
        mMenuRes = TypedAttrUtils.getInteger(attrSet,"menuRes", INT_DEFAULT);
        int customView = TypedAttrUtils.getInteger(attrSet,"menuCustomView", INT_DEFAULT);
        if (customView != INT_DEFAULT) {
            mCustomView = (ComponentContainer) LayoutScatter.getInstance(context)
                    .parse(customView, null, false);
        }
        mDirection = Direction.fromId(TypedAttrUtils.getInteger(attrSet,"menuDirection", 0));
        mTitleTextColor = TypedAttrUtils.getIntColor(
                attrSet,"menuTitleTextColor", getColor(ResourceTable.Color_colorWhite));
        mTitleDisabledTextColor = TypedAttrUtils.getIntColor(
                attrSet,"menuTitleDisabledTextColor", getColor(ResourceTable.Color_darkeGray));
        isShowTitle = TypedAttrUtils.getBoolean(attrSet,"showTitle", true);
        isShowIcon = TypedAttrUtils.getBoolean(attrSet,"showIcon", true);
        isShowOverlay = TypedAttrUtils.getBoolean(attrSet,"showOverlay", true);

        // size
        mMenuSize = TypedAttrUtils.getInteger(attrSet,"menuSize", FAB_MENU_SIZE_NORMAL);
        mMenuCornerRadius = TypedAttrUtils.getInteger(attrSet,"menuCornerRadius", INT_10);
        mDuration = TypedAttrUtils.getInteger(attrSet,"duration", INT_500);

        // Font
        String font = TypedAttrUtils.getString(attrSet,"menuTitleFontFamily", "");
        mMenuTitleTypeface = TypedAttrUtils.getFont(context,font);
        viewHelper = new ViewHelper(context);
        animationHelper = new AnimationHelper(mDuration);

        // initialization
        if (mMenuRes != INT_DEFAULT) {
            setMenu(mMenuRes);
        } else if (mCustomView != null) {
            setCustomView(mCustomView);
        }
    }

    /**
     * getCustomView
     *
     * @return Component
     */
    public Component getCustomView() {
        return mCustomView;
    }

    /**
     * Set custom view as menu
     *
     * @param view custom view
     */
    public void setCustomView(ComponentContainer view) {
        mMenuRes = INT_DEFAULT;
        removeAllComponents();
        mCustomView = view;
        mCustomView.setClickable(true);
        viewHelper.setLayoutParams(mCustomView);
        setUpView(mCustomView, false);
    }

    public void setNestedScrollingEnabled(boolean isEnabled) {
        isEnableNestedScrolling = isEnabled;
    }

    /**
     * Set menu from menu xml
     *
     * @param menuRes menu xml resource
     */
    public void setMenu(int menuRes) {
        mCustomView = null;
        mMenuRes = menuRes;
        removeAllComponents();
        if (menuList != null && menuList.size() > 0) {
            setUpMenuView();
        }
    }

    /**
     * updateMenu
     */
    public void updateMenu() {
        mCustomView = null;
        removeAllComponents();
        if (menuList.size() > 0) {
            setUpMenuView();
        } else {
            setMenu(mMenuRes);
        }
    }

    /**
     * Set menu from list of items
     *
     * @param list list of items
     * @throws NullPointerException NullPointerException
     */
    public void setMenuItems(ArrayList<FABMenuItem> list) throws NullPointerException {
        this.menuList = list;

        mMenuRes = INT_DEFAULT;
        mCustomView = null;
        if (menuList == null) {
            return;
        }
        removeAllComponents();
        setUpMenuView();
    }

    private void setUpMenuView() {
        if (menuList != null && menuList.size() > 0) {
            boolean isCircularShape = false;
            mMenuView = viewHelper.generateMenuView(isEnableNestedScrolling);

            // set layout manager
            if (mDirection == Direction.LEFT || mDirection == Direction.RIGHT) {
                int minItemWidth = isMenuSmall() ? (int)getFloat(ResourceTable.Float_column_size_small)
                        : (int)getFloat(ResourceTable.Float_column_size);
                int rowLayoutResId = isMenuSmall() ? ResourceTable.Layout_row_horizontal_menu_item_small
                        : ResourceTable.Layout_row_horizontal_menu_item;
                mMenuView.setLayoutManager(new DynamicGridLayoutManager(mContext, minItemWidth, menuList.size()));
                menuAdapter = new FABMenuProvider(this, menuList, rowLayoutResId,
                        isCircularShape, mTitleTextColor, mTitleDisabledTextColor, isShowTitle, isShowIcon, mDirection);
                if (mMenuTitleTypeface != null) {
                    menuAdapter.setMenuTitleTypeface(mMenuTitleTypeface);
                }
            } else {
                isCircularShape = !isShowTitle;
                int rowLayoutResId = isMenuSmall() ? ResourceTable.Layout_row_vertical_menu_item_small
                        : ResourceTable.Layout_row_vertical_menu_item;
                mMenuView.setLayoutManager(new DynamicGridLayoutManager(mContext, 0, 0));
                menuAdapter = new FABMenuProvider(this, menuList, rowLayoutResId,
                        isCircularShape, mTitleTextColor, mTitleDisabledTextColor, isShowTitle, isShowIcon, mDirection);
                if (mMenuTitleTypeface != null) {
                    menuAdapter.setMenuTitleTypeface(mMenuTitleTypeface);
                }
            }
            mMenuView.setItemProvider(menuAdapter);
            setUpView(mMenuView, isShowTitle && !isCircularShape);
        }
    }

    private void setUpView(Component component, boolean isToSetMinWidth) {
        mBaseView = viewHelper.generateBaseView(mMenuCornerRadius,mMenuBackground);
        mRevealView = viewHelper.generateRevealView();
        mOverlayLayout = viewHelper.generateOverlayView(isShowOverlay,mOverlayBackground);
        if (isToSetMinWidth) {
            mBaseView.setMinWidth(isMenuSmall() ? (int)getFloat(ResourceTable.Float_menu_min_width_small)
                    : (int)getFloat(ResourceTable.Float_menu_min_width));
        }

        // 1.add menu view
        mBaseView.addComponent(component);

        // 2.add base view
        mRevealView.addComponent(mBaseView);

        // 3.add overlay
        if (mOverlayLayout != null) {
            addComponent(mOverlayLayout);
        }

        // 4.add reveal view
        addComponent(mRevealView);

        if (mOverlayLayout != null) {
            mOverlayLayout.setClickedListener(v -> closeMenu());
        }
    }

    /**
     * Attach fab to menu
     *
     * @param fab fab view
     */
    public void bindAnchorView(ComponentContainer fab) {
        mFab = fab;
        mFab.setClickedListener(v -> showMenu());
        viewHelper.alignMenuWithFab(mFab, mRevealView, mDirection);
    }

    /**
     * action methods
     *
     * @param index index
     * @return FABMenuItem
     */
    public FABMenuItem getItemByIndex(int index) {
        if (menuAdapter != null) {
            return menuAdapter.getItemByIndex(index);
        }
        return null;
    }

    /**
     * getItemById
     *
     * @param id id
     * @return FABMenuItem
     */
    public FABMenuItem getItemById(int id) {
        if (menuAdapter != null) {
            return menuAdapter.getItemById(id);
        }
        return null;
    }

    /**
     * Remove menu item by id
     *
     * @param id id
     * @return boolean
     */
    public boolean removeItem(int id) {
        if (menuList != null) {
            for (int i = 0; i < menuList.size(); i++) {
                if (menuList.get(i).getId() == id) {
                    menuList.remove(i);
                    if (menuAdapter != null) {
                        menuAdapter.notifyDataSetItemRemoved(i);
                        menuAdapter.notifyDataSetItemRangeChanged(i, menuList.size());
                    }
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * notifyItemChanged
     *
     * @param id id
     */
    public void notifyItemChanged(int id) {
        if (menuAdapter != null) {
            menuAdapter.notifyItemChangedById(id);
        }
    }

    /**
     * setOnFABMenuSelectedListener
     *
     * @param listener listener
     */
    public void setOnFABMenuSelectedListener(OnFABMenuSelectedListener listener) {
        this.menuSelectedListener = listener;
    }

    /**
     * isShowing
     *
     * @return boolean
     */
    public boolean isShowing() {
        return fabCurrentState == FAB_STATE_EXPANDED;
    }

    /**
     * Show the menu
     */
    public void showMenu() {
        if (mFab == null || animationHelper.isShowMenuAni()) {
            return;
        }
        if (fabCurrentState == FAB_STATE_COLLAPSED) {
            fabCurrentState = FAB_STATE_EXPANDED;
            viewHelper.alignMenuWithFab(mFab, mRevealView, mDirection);
            animationHelper.revealMenu(this, mFab, mRevealView, false);
            if (isShowOverlay) {
                animationHelper.showOverlay(mOverlayLayout);
            }
        }
    }

    /**
     * Close the menu
     *
     * @throws IllegalStateException IllegalStateException
     */
    public void closeMenu() throws IllegalStateException {
        if (mFab == null || animationHelper.isShowMenuAni()) {
            return;
        }

        if (fabCurrentState == FAB_STATE_EXPANDED) {
            fabCurrentState = FAB_STATE_COLLAPSED;
            animationHelper.revealMenu(this, mFab, mRevealView, true);
            if (isShowOverlay) {
                animationHelper.hideOverlay(mOverlayLayout);
            }
        }
    }

    private void recreateView() {
        if (mMenuRes != INT_DEFAULT) {
            updateMenu();
        } else if (mCustomView != null) {
            setCustomView(mCustomView);
        } else if (menuList != null) {
            setMenuItems(menuList);
        }
    }

    private int getColor(int colorResId) {
        ohos.global.resource.ResourceManager resManager = mContext.getResourceManager();
        try {
            return resManager.getElement(colorResId).getColor();
        } catch (IOException | NotExistException | WrongTypeException e) {
            return INT_DEFAULT;
        }
    }

    private float getFloat(int floatResId) {
        ohos.global.resource.ResourceManager resManager = mContext.getResourceManager();
        try {
            return resManager.getElement(floatResId).getFloat();
        } catch (IOException | NotExistException | WrongTypeException e) {
            return INT_DEFAULT;
        }
    }

    /**
     * setOverlayBackground
     *
     * @param overlayBackground overlayBackground
     * @throws NullPointerException NullPointerException
     */
    public void setOverlayBackground(int overlayBackground) throws NullPointerException {
        this.mOverlayBackground = overlayBackground;
        if (mOverlayLayout != null) {
            ShapeElement shapeElementOver = new ShapeElement();
            shapeElementOver.setRgbColor(viewHelper.getRgbColor(mOverlayBackground));
            mOverlayLayout.setBackground(shapeElementOver);
        }
    }

    /**
     * setMenuBackground
     *
     * @param menuBackgroundRes menuBackgroundRes
     */
    public void setMenuBackground(int menuBackgroundRes) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(viewHelper.getRgbColor(menuBackgroundRes));
        mBaseView.setBackground(shapeElement);
    }

    public boolean isShowOverlay() {
        return isShowOverlay;
    }

    /**
     * setShowOverlay
     *
     * @param isShow isShow
     */
    public void setShowOverlay(boolean isShow) {
        this.isShowOverlay = isShow;
        closeMenu();
        recreateView();
    }

    private boolean isMenuSmall() {
        return mMenuSize == FAB_MENU_SIZE_SMALL;
    }

    /**
     * Set small size for menu item
     */
    public void setSmallerMenu() {
        mMenuSize = FAB_MENU_SIZE_SMALL;
        recreateView();
    }

    /**
     * Set normal size for menu item
     */
    public void setNormalMenu() {
        mMenuSize = FAB_MENU_SIZE_NORMAL;
        recreateView();
    }

    /**
     * setTitleVisible
     *
     * @param isShow isShow
     */
    public void setTitleVisible(boolean isShow) {
        this.isShowTitle = isShow;
        if (menuAdapter != null) {
            if (isShowTitle && (mDirection == Direction.UP || mDirection == Direction.DOWN)) {
                mBaseView.setMinWidth((int)getFloat(ResourceTable.Float_menu_min_width));
            } else {
                mBaseView.setMinWidth(ComponentContainer.LayoutConfig.MATCH_CONTENT);
            }
            menuAdapter.setShowTitle(isShowTitle);
            closeMenu();
            recreateView();
        }
    }

    /**
     * setMenuTitleTextColor
     *
     * @param titleTextColor titleTextColor
     */
    public void setMenuTitleTextColor(int titleTextColor) {
        this.mTitleTextColor = titleTextColor;
        if (menuAdapter != null) {
            menuAdapter.setTitleTextColor(mTitleTextColor);
            menuAdapter.notifyDataChanged();
        }
    }

    /**
     * setMenuTitleDisabledTextColor
     *
     * @param titleDisabledTextColor titleDisabledTextColor
     */
    public void setMenuTitleDisabledTextColor(int titleDisabledTextColor) {
        this.mTitleDisabledTextColor = titleDisabledTextColor;
        if (menuAdapter != null) {
            menuAdapter.setTitleDisabledTextColor(mTitleDisabledTextColor);
            menuAdapter.notifyDataChanged();
        }
    }

    public Direction getMenuDirection() {
        return mDirection;
    }

    /**
     * setMenuDirection
     *
     * @param direction direction
     */
    public void setMenuDirection(Direction direction) {
        this.mDirection = direction;
        if (menuAdapter != null) {
            menuAdapter.setDirection(mDirection);
            recreateView();
        }
    }

    /**
     * setMenuTitleTypeface
     *
     * @param menuTitleTypeface menuTitleTypeface
     */
    public void setMenuTitleTypeface(String menuTitleTypeface) {
        if (menuTitleTypeface != null) {
            this.mMenuTitleTypeface = TypedAttrUtils.getFont(getContext(),menuTitleTypeface);
            recreateView();
        }
    }

    public AnimationHelper getAnimationHelper() {
        return animationHelper;
    }
}
